<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<!--[if lt IE 9]>
  <script src="https://oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js"></script>
<![endif]-->
<title>VIM 中文帮助: 撤销和重做</title>
<link rel="stylesheet" href="vim-stylesheet.css" type="text/css" />
<link rel="canonical" href="https://yianwillis.github.io/vimcdoc/doc/undo.html" />
<script type="text/javascript" src="vimcdoc.js"></script>
<meta name="viewport" content="width=device-width, initial-scale=1" />
</head>
<body>
<nav id=banner>
<form action=tags.html target="tag_iframe">
  <input type="text" name="tag" id="tag" placeholder="标签搜索">
</form>
<iframe name="tag_iframe" src=""></iframe>
<a href="help.html">帮助总览</a> &middot;
<hr/>
<a href="quickref.html">快速参考</a> &middot;
<a href="index.html">命令索引</a> &middot;
<a href="eval.html#functions">函数列表</a> &middot;
<a href="quickref.html#option-list">选项列表</a> &middot;
<hr/>
<a href="usr_toc.html">用户手册</a> &middot;
<a href="help.html#reference_toc">参考手册</a> &middot;
</nav>

<header>
<h2>undo</h2>
</header>
<article id=outer>
<section class=inner>
<b class="vimtag"> <a name="undo.txt">undo.txt</a> </b>      适用于 Vim 9.0 版本。   最近更新: 2022年8月


                  <code class="vim">VIM 参考手册    by Bram Moolenaar</code>
                                <code class="vim">译者</code>: jwdu、tocer

撤销和重做                                                      <b class="vimtag"> <a name="undo-redo">undo-redo</a> </b>

在用户手册的  <a href="usr_02.html#02.5">02.5</a>  节对它们的基本操作有解释。

1. 撤销和重做操作的命令          <a href="undo.html#undo-commands">undo-commands</a> 
2. 撤销操作的两种方法            <a href="undo.html#undo-two-ways">undo-two-ways</a> 
3. 撤销块                        <a href="undo.html#undo-blocks">undo-blocks</a> 
4. 撤销分支                      <a href="undo.html#undo-branches">undo-branches</a> 
5. 撤销的持久性                  <a href="undo.html#undo-persistence">undo-persistence</a> 
6. 撤销操作的解释                <a href="undo.html#undo-remarks">undo-remarks</a> 

</section><hr class="doubleline" /><section class=inner>
<h4>1. 撤销和重做命令                                       <b class="vimtag"> <a name="undo-commands">undo-commands</a> </b></h4>
<code class="special">&lt;Undo&gt;</code>          或                                      <b class="vimtag"> <a name="undo">undo</a> </b> <b class="vimtag"> <a name="%3CUndo%3E">&lt;Undo&gt;</a> </b> <b class="vimtag"> <a name="u">u</a> </b>
u                       撤销 <code class="special">[count]</code> 次更改。

                                                        <b class="vimtag"> <a name=":u">:u</a> </b> <b class="vimtag"> <a name=":un">:un</a> </b> <b class="vimtag"> <a name=":undo">:undo</a> </b>
:u[ndo]                 撤销一次更改。

                                                                <b class="vimtag"> <a name="E830">E830</a> </b>
:u[ndo] <code class="special">{N}</code>             转到改变号 <code class="special">{N}</code> 之后。<code class="special">{N}</code> 的含义参见  <a href="undo.html#undo-branches">undo-branches</a> 。

                                                        <b class="vimtag"> <a name="CTRL-R">CTRL-R</a> </b>
<code class="keystroke">CTRL-R</code>                  重做 <code class="special">[count]</code> 次被撤销的更改。

                                                        <b class="vimtag"> <a name=":red">:red</a> </b> <b class="vimtag"> <a name=":redo">:redo</a> </b> <b class="vimtag"> <a name="redo">redo</a> </b>
:red[o]                 重做一个被撤销的更改。

                                                        <b class="vimtag"> <a name="U">U</a> </b>
U                       撤销最近对特定行所作的一系列更改，也就是最近作改变的
                        那行。 <a href="undo.html#U">U</a>  自己也算一次改变，因此  <a href="undo.html#U">U</a>  会撤销之前的
                         <a href="undo.html#U">U</a> 。

由于最近所作的更改是被记住的，因此，你可以应用上面的撤销和重做命令把文件倒转到
你所作每次更改以前的状态。当然，你也可以重新应用这些修改，把文件重做到执行撤销
操作以前的状态。

对于撤销/重做 (undo/redo) 来说，"U" 命令和其他命令是同等对待的。所以 "u" 可以
撤销一个 "U" 命令、'<code class="keystroke">CTRL-R</code>' 也可以重做对 "U" 命令的撤销。当 "U"、"u" 和
'<code class="keystroke">CTRL-R</code>' 三个命令混合使用时，你将<code class="note">注意</code>到 "U" 命令决意将被该行还原到前一个 "U"
命令之前的状态。这或许会令你困惑。多试试，你会习惯的。
"U" 命令总是把缓冲区标记为已改变。即使它把修改过的缓冲区重做到未改变的状态也是
如此。为此，只有使用 "u" 命令，才能令缓冲区正确地回复到未改变的标记。

</section><hr class="doubleline" /><section class=inner>
<h4>2. 撤销操作的两种方法                                   <b class="vimtag"> <a name="undo-two-ways">undo-two-ways</a> </b></h4>
撤销和重做的工作模式依赖于 <a href="options.html#'cpoptions'">'cpoptions'</a> 中 'u' 标志位。这里有两种模式，Vim 模式
(不包含 'u') 和 Vi-兼容模式 (包含 'u')。在 Vim 模式下，"uu" 命令撤销两次改变，
在 Vi-兼容模式下，"uu" 命令什么也不做 (撤销第一次撤销)。

不包含 'u' 的 Vim 模式:
及时的撤销命令可以回到先前的状态。你也可以使用重做命令再次前进。不过，如果在撤
销命令后你做了一个新的改变，重做上次的撤销便不再可能。

包含 'u' 的 Vi-兼容模式:
撤销命令撤销包含以前的撤销命令在内的任何更改。重做命令重复前一个撤销命令。但它
 <code class="emphasis">不</code> 用来重复改变命令。为此请用 "." 命令。

<code class="section">举例            Vim 模式                Vi-兼容模式     </code>
"uu"            两次撤销                空操作
"u <code class="keystroke">CTRL-R</code>"      空操作                  两次撤销

原理:   Nvi 使用 "." 命令而不是 <code class="keystroke">CTRL-R</code>。不幸的是，这不是 Vi 兼容的。例如
        "dwdwu." 命令，在 Vi 中删除两个词，但在 Nvi 中，它什么也不做。

</section><hr class="doubleline" /><section class=inner>
<h4>3. 撤销块                                               <b class="vimtag"> <a name="undo-blocks">undo-blocks</a> </b></h4>
单个 undo 命令通常撤掉一个输入的命令，不论这个命令造成多少改变。这个可以撤销的
改变序列构成了一个撤销块。所以如果键入的命令调用一个函数，那么在这个函数中的所
有命令全部被撤销。

如果你想编写一个函数或脚本，它不包含新的可撤销之改变，而合并到之前的改变中，用
以下命令:

                                                <b class="vimtag"> <a name=":undoj">:undoj</a> </b> <b class="vimtag"> <a name=":undojoin">:undojoin</a> </b> <b class="vimtag"> <a name="E790">E790</a> </b>
:undoj[oin]             把其后的改变和以前的撤销块进行合并。
                        <code class="note">警告</code>: 小心使用。它会阻止用户合理地撤销改变。不要在撤销
                        或重做后使用。

这最适用于在改变的过程中需要提示用户的场合。例如调用  <a href="builtin.html#getchar()">getchar()</a>  的函数。确保
在这之前有一个相关的必须与之合并的改变。

这条命令不能单独工作，因为下一个键击会再次造成新改变。但你可以这么做: 
<code class="example"></code>
<code class="example">        :undojoin | delete</code>
<code class="example"></code>
在此之后，"u" 命令会同时撤销 delete 命令和前一次改变。
                                        <b class="vimtag"> <a name="undo-break">undo-break</a> </b> <b class="vimtag"> <a name="undo-close-block">undo-close-block</a> </b>
要反过来，要让下一改变启用新撤销块，在插入模式下用 <code class="keystroke">CTRL-G</code> u。可用于使某个插入
命令部分可撤销。譬如可分拆到每个句子。 <a href="insert.html#i_CTRL-G_u">i_CTRL-G_u</a> 
设置 <a href="options.html#'undolevels'">'undolevels'</a> 的值也会结束撤销块。即使新值和旧值相同。 <a href="vim9.html#Vim9">Vim9</a>  脚本里: 
<code class="example">        &amp;undolevels = &amp;undolevels</code>
老式脚本里: 
<code class="example">        let &amp;undolevels = &amp;undolevels</code>
<code class="example"></code>
</section><hr class="doubleline" /><section class=inner>
<h4>4. 撤销分支                                     <b class="vimtag"> <a name="undo-branches">undo-branches</a> </b> <b class="vimtag"> <a name="undo-tree">undo-tree</a> </b></h4>
上面我们只讨论了单线的撤销/重做。但你也可以进行分支。考虑你先撤销了若干改变，
然后又进行了一些其它的改变。此时，被撤销的改变就成为一个分支。下面的命令使你能
够到达那个分支。

这在用户手册中有解释:  <a href="usr_32.html#usr_32.txt">usr_32.txt</a> 。

                                                        <b class="vimtag"> <a name=":undol">:undol</a> </b> <b class="vimtag"> <a name=":undolist">:undolist</a> </b>
:undol[ist]             列出改变树的所有叶结点。例如:
<code class="section">                           number changes  when               saved </code>
                               88      88  2010/01/04 14:25:53
                              108     107  08/07 12:47:51
                              136      46  13:33:01             7
                              166     164  3 seconds ago

                        "number" 列是改变号。这个编号持续增加，用于标识特定可
                        撤销的改变，参见  <a href="undo.html#:undo">:undo</a> 。
                        "changes" 列是树的根结点到此叶结点所需的改变数目。
                        "when" 列是此改变发生的日期时间。四种可能的格式是:
                            N seconds ago
                            HH:MM:SS             时分秒
                            MM/DD HH:MM:SS       同上，还有月日
                            YYYY/MM/DD HH:MM:SS  同上，还有年
                        "save" 列给出此改变是否已写入硬盘和第几次写入文件。可
                        用于  <a href="undo.html#:later">:later</a>  和  <a href="undo.html#:earlier">:earlier</a>  命令。
                        要更详细的信息可用  <a href="builtin.html#undotree()">undotree()</a>  函数。

                                                        <b class="vimtag"> <a name="g-">g-</a> </b>
g-                      转到较早的文本状态。如果带计数，重复那么多次。
                                                        <b class="vimtag"> <a name=":ea">:ea</a> </b> <b class="vimtag"> <a name=":earlier">:earlier</a> </b>
:earlier <code class="special">{count}</code>        转到 <code class="special">{count}</code> 次较早的文本状态。
:earlier <code class="special">{N}</code>s           转到大约 <code class="special">{N}</code> 秒钟之前的较早的文本状态。
:earlier <code class="special">{N}</code>m           转到大约 <code class="special">{N}</code> 分钟之前的较早的文本状态。
:earlier <code class="special">{N}</code>h           转到大约 <code class="special">{N}</code> 小时之前的较早的文本状态。
:earlier <code class="special">{N}</code>d           转到大约 <code class="special">{N}</code> 天之前的较早的文本状态。

:earlier <code class="special">{N}</code>f           转到 <code class="special">{N}</code> 次文件写入之前的较早的文本状态。
                        如果上次写入之后有改动，":earlier 1f" 会恢复文本到上次
                        写入时的状态。否则会转到再上一次写入时的状态。
                        如果在第一次文件写入的状态，甚至于从未写入过文件，
                        ":earlier 1f" 会转到首次改变之前的状态。

                                                        <b class="vimtag"> <a name="g+">g+</a> </b>
g+                      转到较新的文本状态。如果带计数，重复那么多次。
                                                        <b class="vimtag"> <a name=":lat">:lat</a> </b> <b class="vimtag"> <a name=":later">:later</a> </b>
:later <code class="special">{count}</code>  转到 <code class="special">{count}</code> 次较新的文本状态。
:later <code class="special">{N}</code>s             转到大约 <code class="special">{N}</code> 秒钟之后的较新的文本状态。
:later <code class="special">{N}</code>m             转到大约 <code class="special">{N}</code> 分钟之后的较新的文本状态。
:later <code class="special">{N}</code>h             转到大约 <code class="special">{N}</code> 小时之后的较新的文本状态。
:later <code class="special">{N}</code>d             转到大约 <code class="special">{N}</code> 天之后的较新的文本状态。

:later <code class="special">{N}</code>f             转到 <code class="special">{N}</code> 次文件写入之后较新的文本状态。
                        若在在最后一次文件写入的状态，":later 1f" 会转到最新的
                        文本状态。

<code class="note">注意</code> 如果由于 <a href="options.html#'undolevels'">'undolevels'</a> 选项，撤销信息被清空，那么文本状态将无法访问。

在不同时间点上移动的时候，不要奇怪一个时间点会同时发生多个改变。通过撤销树跳转
然后又做了新的改变后就会发生这种情况。

示 例

从这一行开始:
<code class="section">        one two three </code>

按三次 "x" 删除第一个单词:
<code class="section">        ne two three </code>
<code class="section">        e two three </code>
<code class="section">         two three </code>

现在按 "u" 三次撤销:
<code class="section">        e two three </code>
<code class="section">        ne two three </code>
<code class="section">        one two three </code>

按三次 "x" 删除第二个单词:
<code class="section">        one wo three </code>
<code class="section">        one o three </code>
<code class="section">        one  three </code>

现在按 "g-" 三次撤销:
<code class="section">        one o three </code>
<code class="section">        one wo three </code>
<code class="section">         two three </code>

现在，回到第一个撤销分支，也就是在删除 "one" 之后。重复 "g-" 会回到原始文本:
<code class="section">        e two three </code>
<code class="section">        ne two three </code>
<code class="section">        one two three </code>

使用 ":later 1h" 跳到最后一次改变:
<code class="section">        one  three </code>

使用 ":earlier 1h" 再次回到开始:
<code class="section">        one two three </code>


<code class="note">注意</code> 使用 "u" 和 <code class="keystroke">CTRL-R</code> 无法得到全部可能的文本状态，但是重复 "g-" 和 "g+" 却可
以。

</section><hr class="doubleline" /><section class=inner>
<h4>5. 撤销的持久性                 <b class="vimtag"> <a name="undo-persistence">undo-persistence</a> </b> <b class="vimtag"> <a name="persistent-undo">persistent-undo</a> </b></h4>
卸载缓冲区时，Vim 通常会删除该缓冲区建立的撤销树。通过设置 <a href="options.html#'undofile'">'undofile'</a> 选项，
Vim 会在写入文件时自动保存撤销历史，而重新编辑文件时，恢复撤销历史。

<a href="options.html#'undofile'">'undofile'</a> 选项在写入文件之后检查，而发生在 BufWritePost 自动命令之前。要控制
哪些文件需要保存撤销信息，可以使用 BufWritePre 自动命令: 
<code class="example">        au BufWritePre /tmp/* setlocal noundofile</code>
<code class="example"></code>
Vim 把撤销树保存在一个独立的撤销文件里，每个编辑的文件对应一个，使用一个简单的
方案来对应文件系统的路径到撤销文件名。Vim 会检测是否某个撤销文件不再和写它时的
那个文件同步 (使用文件内容的哈希值)，如果文件内容在撤销文件写入后有改动，忽略
撤销文件，以防止文件遭破坏。如果撤销文件的拥有者和正在编辑文件的不同，也忽略
之，除非撤销文件的拥有者是当前用户。设置 <a href="options.html#'verbose'">'verbose'</a> 可获取关于打开文件的消息。

撤销文件通常保存在文件本身相同的目录里。这可以用 <a href="options.html#'undodir'">'undodir'</a> 选项改变。

如果文件被加密，撤销文件的文件也会加密。使用相同的密钥和方法。 <a href="editing.html#encryption">encryption</a> 

<code class="note">备注</code> 撤销文件不保存文本属性。只要缓冲区载入了就可以恢复文本属性，但你不能从撤
销文件中恢复。理据: 这需要和之前以完全相同的方式定义相关联的文本属性类型，而这
不能保证。

你也可以用 ":wundo" 和 ":rundo" 来相应地保存和恢复撤销历史:
                                                        <b class="vimtag"> <a name=":wundo">:wundo</a> </b> <b class="vimtag"> <a name=":rundo">:rundo</a> </b>
:wundo[!] <code class="special">{file}</code>
                把撤销历史写入 <code class="special">{file}</code>。
                如果 <code class="special">{file}</code> 已存在而看起来不像撤销文件 (文件头部的魔术数字不
                符)，此命令失败。除非加上 !。
                如果文件存在且看起来像撤销文件，覆盖之。如果没有撤销历史，不
                写入任何东西。
                实现细节: 覆盖操作如此执行，先删除已有的文件，然后建立同名的
                文件。所以，不能在写保护目录里覆盖已有的撤销文件。

:rundo <code class="special">{file}</code>   从 <code class="special">{file}</code> 读出撤销历史。

你可以在自动命令中用这些命令来显式指定历史文件名。例如: 
<code class="example"></code>
<code class="example">        au BufReadPost * call ReadUndo()</code>
<code class="example">        au BufWritePost * call WriteUndo()</code>
<code class="example">        func ReadUndo()</code>
<code class="example">          if filereadable(expand('%:h') .. '/UNDO/' .. expand('%:t'))</code>
<code class="example">            rundo %:h/UNDO/%:t</code>
<code class="example">          endif</code>
<code class="example">        endfunc</code>
<code class="example">        func WriteUndo()</code>
<code class="example">          let dirname = expand('%:h') .. '/UNDO'</code>
<code class="example">          if !isdirectory(dirname)</code>
<code class="example">            call mkdir(dirname)</code>
<code class="example">          endif</code>
<code class="example">          wundo %:h/UNDO/%:t</code>
<code class="example">        endfunc</code>
<code class="example"></code>
此时，应该关闭 <a href="options.html#'undofile'">'undofile'</a>，否则每次写入文件时会有两个撤销文件。

可以用  <a href="builtin.html#undofile()">undofile()</a>  函数来确定 Vim 会使用的文件名。

<code class="note">注意</code>: 读写文件时，如果设置了 <a href="options.html#'undofile'">'undofile'</a>，大部分的错误信息会被屏蔽，除非设置了
<a href="options.html#'verbose'">'verbose'</a>。而 :wundo 和 :rundo 会得到更到的错误信息，如文件不能读或写等。

<code class="note">注意</code>: Vim 从不删除撤销文件。你需要自己进行清除。

读出已经存在的撤销文件可能会有以下的失败原因:
<b class="vimtag"> <a name="E822">E822</a> </b>  不能打开，文件许可权限不许可。
<b class="vimtag"> <a name="E823">E823</a> </b>  文件头的魔术数字不符。通常这意味着这不是一个撤销文件。
<b class="vimtag"> <a name="E824">E824</a> </b>  撤销文件的版本号说明它是由更新的 Vim 版本写入的。需要更新的版本来读。
        如果你想保留该文件中的撤销信息，不要写入缓冲区。
"File contents changed, cannot use undo info"
        文件文本和撤销文件写入时的不同。这意味着不再能使用该撤销文件，否则会破
        坏文本。这也可能是因为 <a href="options.html#'encoding'">'encoding'</a> 和撤销文件写入时的不同。
<b class="vimtag"> <a name="E825">E825</a> </b>  撤销文件没有合法的内容，不能使用。
<b class="vimtag"> <a name="E826">E826</a> </b>  撤销文件经过加密而解密失败。
<b class="vimtag"> <a name="E827">E827</a> </b>  撤销文件经过加密而此版本的 Vim 不支持加密。用别的 Vim 打开该文件。
<b class="vimtag"> <a name="E832">E832</a> </b>  撤销文件经过加密而 <a href="options.html#'key'">'key'</a> 没有设置，文本文件本身没有加密。这可能是因为
        文本文件本来是由 Vim 用加密方式写入的，后来又用非加密方式进行了覆盖。
        最好删除该撤销文件。
"Not reading undo file, owner differs"
        撤销文件的拥有者和文本文件的拥有者不同。因为安全原因，不使用该撤销文
        件。

写入撤销文件可能会有以下的失败原因:
<b class="vimtag"> <a name="E828">E828</a> </b>  待写入的文件不能建立。可能你没有该目录的写入权限。
"Cannot write undo file in any directory in <a href="options.html#'undodir'">'undodir'</a>"
        <a href="options.html#'undodir'">'undodir'</a> 里没有一个可用的目录。
"Will not overwrite with undo file, cannot read"
        待写入的撤销文件已经存在，但不能读回。你需要先删除或改名。
"Will not overwrite, this is not an undo file"
        待写入的撤销文件已经存在，但开头没有正确的魔术数字。你需要先删除或改
        名。
"Skipping undo file write, nothing to undo"
        没有可写的撤销信息，没有任何改变，或者 <a href="options.html#'undolevels'">'undolevels'</a> 为负。
<b class="vimtag"> <a name="E829">E829</a> </b>  写入撤销文件时有错。可以多试几次。

</section><hr class="doubleline" /><section class=inner>
<h4>6. 撤销操作的解释                                       <b class="vimtag"> <a name="undo-remarks">undo-remarks</a> </b></h4>
能记忆的最大改变次数由 <a href="options.html#'undolevels'">'undolevels'</a> 选项决定。如果它的值是零，我们总是运行在
Vi-兼容模式。如果它的值是负的，任何撤销都是不可能的。这只有在内存紧张的时候适
用。

                                                        <b class="vimtag"> <a name="clear-undo">clear-undo</a> </b>
如果设置 <a href="options.html#'undolevels'">'undolevels'</a> 为 -1，撤销信息不会立即清除。当有新改变时才会。要强迫清
除撤销信息，可以用: 
<code class="example">        :let old_undolevels = &amp;undolevels</code>
<code class="example">        :set undolevels=-1</code>
<code class="example">        :exe "normal a \&lt;BS&gt;\&lt;Esc&gt;"</code>
<code class="example">        :let &amp;undolevels = old_undolevels</code>
<code class="example">        :unlet old_undolevels</code>
<code class="example"></code>
当前缓冲区的位置标记 ('a 至 'z) 随文本一起被保存和复原。

当所有的改变都被撤销时，缓冲区被标记为未改变。这时可以使用 ":q" 而不一定是
":q!" 退出 Vim 。<code class="note">注意</code> 未改变是相对文件的最后写入而言的。在写入 ":w" 后紧跟的撤
销 "u"，实际上改变了被写入以后缓冲区的状态。因此，此时缓冲区应被视为已改变。

当使用手动  <a href="fold.html#folding">folding</a>  时，折叠不会被保存和复原。只有完全发生折叠内部的改变才不
会影响该折叠，因为它开头和最后一行没有改变。

数字编号的寄存器也可以被用作撤销删除操作。你每一次删除文本，该文本被放在 "1 寄
存器中。同样的，"1 寄存器的内容被移到 "2 寄存器，依次类推。"9 寄存器的内容则会
丢失。现在，你可以通过命令 '"1P' 得到最近删除的文本。(如果被删除的内容来自最近
的删除或复制操作，'P' 或 'p' 同样也可得到你要的结果，因为他们会复制未命名寄存
器的内容)。使用 '"3P' 可以得到三个删除操作之前的文本。

                                                        <b class="vimtag"> <a name="redo-register">redo-register</a> </b>
如果你想得到多于一处被删除文本的内容，你可以使用重复命令 "." 一个特殊的特性:
它会递增所使用寄存器内的序号。所以，你如果先使用 '"1P' ，那么接下来的 "." 就意
味着 '"2P'。重复这样的操作，所有编号的寄存器都会被插入。

例如: 如果你用 'dd....' 删除了内容 (五行文本)，那么用 '"1P....' 可以恢复之。

如果你不知道被删除的内容在哪一个寄存器，你可以用  <a href="change.html#:display">:display</a>  命令。还有一个方法
就是先试第一个寄存器 '"1P' ，如果不对，用 'u.' 命令。这将会移走第一次放进的内
容，然后在第二个寄存器上重复 put 命令。重复使用 'u.' 直到你得到想要的内容为
止。

 vim:tw=78:ts=8:noet:ft=help:norl:
</section>
</article>
<footer>
Generated by vim2html
</footer>
</body>
</html>
